package cn.hh.ymcc.service.impl;

import cn.hh.ymcc.domain.AlipayInfo;
import cn.hh.ymcc.domain.CourseOrder;
import cn.hh.ymcc.domain.PayFlow;
import cn.hh.ymcc.domain.PayOrder;
import cn.hh.ymcc.dto.AlipayNotifyDto;
import cn.hh.ymcc.dto.ApplyPayDto;
import cn.hh.ymcc.dto.PyaResultDto;
import cn.hh.ymcc.enums.GlobalExceptionCode;
import cn.hh.ymcc.service.IAlipayInfoService;
import cn.hh.ymcc.service.IPayFlowService;
import cn.hh.ymcc.service.IPayOrderService;
import cn.hh.ymcc.service.IPayService;
import cn.hh.ymcc.util.AssertUtil;
import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class PayServiceImpl implements IPayService {

    @Autowired
    private IPayOrderService payOrderService;

    @Autowired
    private IAlipayInfoService alipayInfoService;

    @Autowired
    private IPayFlowService payFlowService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;


    /**
     * 发起支付
     *
     * @param dto
     * @return
     */
    @Override
    public String apply(ApplyPayDto dto) {
        // 判断前端传过来的参数是否存在
        AssertUtil.isNotNull(dto, GlobalExceptionCode.SERVICE_PARAM_IS_NULL.getMessage());
        Wrapper<PayOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", dto.getOrderNo());
        PayOrder payOrder = payOrderService.selectOne(wrapper);
        // 判断支付单是否存在
        AssertUtil.isNotNull(payOrder, GlobalExceptionCode.ORDER_INFO_ERROR.getMessage());
        Integer payType = dto.getPayType().intValue();

        if (payType == ApplyPayDto.ALI_PAY) { // 支付宝支付
            return aliPay(payOrder, dto);
//        } else if (payType == ApplyPayDto.BALANCE_PAY) { // 余额支付
//
//        } else if (payType == ApplyPayDto.WECHAT_PAY) { // 微信支付
//
//        }else if (payType == ApplyPayDto.UNIONPAY_PAY){ // 银联支付

        } else {
            GlobalExceptionCode.PAY_ERROR.getMessage();
        }

        return null;
    }

    /**
     * 支付宝支付异步通知 支付结果
     * @param dto
     */
    @Override
    public String notify(AlipayNotifyDto dto) {
        // 1.验证签名,确保是支付宝给我们发的
        String jsonString = JSON.toJSONString(dto); // 先将支付宝通知返回的参数Dto,转成一个JSON格式的字符串
        Map map = JSON.parseObject(jsonString, Map.class); // 在将这个字符串转成一个Map,传入验签方法中
        try {
            // 如果结果是true,说明验证签名成功
            Boolean isValid = Factory.Payment.Common().verifyNotify(map); // verifyNotify验证签名方法(Map) 需要传入一个Map<>
            // 验证签名之后返回一个结果, 判断这个结果对象是否正确 isValid
            if (!isValid){ // 验签失败
                return "No";
            }
        } catch (Exception e) {
           e.printStackTrace();
            return "No"; // 如果业务异常也返回No
        }
        // ------执行到这里说明.验证签名成功通过-------
        // 校验支付单
        Wrapper<PayOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no",dto.getOut_trade_no()); // 查询支付宝返回的支付单是否存在
        PayOrder payOrder = payOrderService.selectOne(wrapper); // 查询支付单,返回一个支付单对象
        AssertUtil.isNotNull(payOrder,GlobalExceptionCode.ORDER_IS_NULL_ERROR.getMessage()); // 判断支付单是否存在
        // 2.校验订单是否正确,支付金额是否正确====业务校验
        boolean isEq = payOrder.getAmount().compareTo(new BigDecimal(dto.getTotal_amount())) == 0; // 比较支付单的金额 是否与支付宝返回的支付金额相等  0=相等
        AssertUtil.isTrue(isEq,GlobalExceptionCode.ORDER_INFO_ERROR.getMessage()); // 判断支付单金额是否与支付金额相等
        // 3.根据支付宝的支付状态必须是交易成功,执行下面
        AssertUtil.isTrue(dto.isTradeSuccess(),GlobalExceptionCode.ORDER_INFO_ERROR.getMessage());  // 调用dto的isTradeSuccess方法 判断交易状态是否成功
        // 判断支付单的支付状态是否为取消订单
        if (payOrder.getPayStatus() == PayOrder.STATE_CANCEL){
            try {
                // 如果是取消订单,就退款
                Factory.Payment.Common().refund(payOrder.getOrderNo(),payOrder.getAmount().toString());
            } catch (Exception e) {
               e.printStackTrace();
               log.error("支付宝退款异常!订单号为:" + payOrder.getOrderNo());
            }
            return "success";
        }

        // 4.支付服务:
        //      修改支付单状态 + 时间
        Date now = new Date();
        payOrder.setPayStatus(PayOrder.STATE_PAY_SUCCESS);
        payOrder.setUpdateTime(now);
       //  payOrderService.updateById(payOrder);  这里修改支付单状态和时间,应该交给Mq的事务监听器

        //      记录支付流水
        PayFlow flow = new PayFlow();
        flow.setNotifyTime(now);
        flow.setSubject(payOrder.getSubject());
        flow.setOutTradeNo(payOrder.getOrderNo());
        flow.setTotalAmount(payOrder.getAmount());
        flow.setTradeStatus(dto.getTrade_status());
        flow.setPaySuccess(true);
       //  payFlowService.insert(flow); 这里的记录支付流水保存,应该也交个Mq的事务监听器

        // new一个Map,将修改后的支付单和添加的流水记录,保存在map中,作为发送事务消息的生产者的扩展参数: arg=参数中
        HashMap<String, Object> payMap = new HashMap<>();
        payMap.put("payOrder",payOrder);
        payMap.put("payFlow",flow);

        // new一个支付结果Dto,两个参数: orderNo,extParams
        // 将支付单号通过订单号设置, 将扩展参数也通过
        PyaResultDto resultDto = new PyaResultDto(
                payOrder.getOrderNo(),
                payOrder.getExtParams() // 这里的值要追溯到下单服务的 发送保存支付单的事务消息哪里
        );
        //  5.订单服务 + 课程服务 == 通过Mq完成
        //      发送事务消息==
        //      由消费者去广播消息即可
        // 将扩展参数对象,转为Message
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(resultDto)).build();
        rocketMQTemplate.sendMessageInTransaction(
                "TxPayResultListener",
                "topic-payresult:tag-payresult",
                message,  // PyaResultDto
                payMap);  // 扩展参数

        return "success";
    }


    /**
     * 支付宝支付
     * @param payOrder
     * @param dto      沙箱环境: 买家账号: srowfe3362@sandbox.com
     * @return
     */
    public String aliPay(PayOrder payOrder, ApplyPayDto dto) {
        // 查询支付信息表的第一条数据
        AlipayInfo alipayInfo = alipayInfoService.selectList(null).get(0);
        // 1. 设置参数（全局只需设置一次）
        Factory.setOptions(getOptions(alipayInfo));
        try {
            // 2. 发起API调用 网页支付 Payment:支付能力  Page（电脑网站） pay（生成交易表单，渲染后自动跳转支付宝网站引导用户完成支付）
            AlipayTradePagePayResponse response = Factory.Payment.Page().pay(
                    payOrder.getSubject(), // 第一个参数: 标题
                    payOrder.getOrderNo(),  // 第二个参数: 订单号 (支付单号)
                    payOrder.getAmount().toString(),  // 第三个参数: 支付金额
                    StringUtils.hasLength(dto.getCallUrl()) ? dto.getCallUrl() : alipayInfo.getReturnUrl());// 同步回调地址
            // 3. 处理响应或异常
            if (ResponseChecker.success(response)) {
                return response.getBody();
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 支付宝支付配置
     *
     * @param alipayInfo
     * @return
     */
    public Config getOptions(AlipayInfo alipayInfo) {
        Config config = new Config();
        config.protocol = alipayInfo.getProtocol();  // 协议
        config.gatewayHost = alipayInfo.getGatewayHost();  // 网关
        config.signType = alipayInfo.getSignType(); // 加密算法
        config.appId = alipayInfo.getAppId();
        // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
        config.merchantPrivateKey = alipayInfo.getMerchantPrivateKey();
        //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
        config.alipayPublicKey = alipayInfo.getAlipayPublicKey();
        //可设置异步通知接收服务地址（可选）
        config.notifyUrl = alipayInfo.getNotifyUrl();  // 后台的支付服务Controller接口 异步
        return config;
    }



}
